/**
 * Copyright (C) 2013 The CyanogenMod Project
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.whispersystems.whisperpush.crypto;

import android.content.Context;
import android.util.Log;

import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.Util;
import org.whispersystems.whisperpush.util.WhisperPreferences;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

/**
 * This mainly exists as a placeholder, and for TextSecure-library
 * compatibility.  The MasterSecret is used to encrypt any private
 * key material before writing it to disk, and the MasterSecret key
 * material would itself be encrypted using a user specified passphrase.
 *
 * This project does not currently employ a user-specified passphrase
 * for local secret encryption since full disk encryption is available
 * through the system.  However, we still generate a MasterSecret since
 * the TextSecure library functions require it and the overhead is negligable.
 * We just don't encrypt it with anything, meaning that it doesn't provide
 * any protection by design.
 */
public class MasterSecretUtil {

  public static synchronized MasterSecret getMasterSecret(Context context) {
    if (WhisperPreferences.getMasterSecret(context) == null) {
      return generateMasterSecret(context);
    } else {
      MasterSecret masterSecret = retrieveMasterSecret(context);
      return masterSecret == null ? generateMasterSecret(context) : masterSecret;
    }
  }

  private static MasterSecret generateMasterSecret(Context context) {
    byte[] encryptionSecret = generateEncryptionSecret();
    byte[] macSecret        = generateMacSecret();
    byte[] masterSecret     = Util.combine(encryptionSecret, macSecret);

    WhisperPreferences.setMasterSecret(context, Base64.encodeBytes(masterSecret));

    return new MasterSecret(new SecretKeySpec(encryptionSecret, "AES"),
                            new SecretKeySpec(macSecret, "HmacSHA1"));
  }

  private static MasterSecret retrieveMasterSecret(Context context) {
    try {
      byte[] combinedSecrets  = Base64.decode(WhisperPreferences.getMasterSecret(context));
      byte[] encryptionSecret = getEncryptionSecret(combinedSecrets);
      byte[] macSecret        = getMacSecret(combinedSecrets);

      return new MasterSecret(new SecretKeySpec(encryptionSecret, "AES"),
                              new SecretKeySpec(macSecret, "HmacSHA1"));
    } catch (IOException e) {
      Log.w("MasterSecretUtil", e);
      return null;
    }
  }

  private static byte[] getEncryptionSecret(byte[] combinedSecrets) {
    byte[] encryptionSecret = new byte[16];
    System.arraycopy(combinedSecrets, 0, encryptionSecret, 0, encryptionSecret.length);
    return encryptionSecret;
  }

  private static byte[] getMacSecret(byte[] combinedSecrets) {
    byte[] macSecret = new byte[20];
    System.arraycopy(combinedSecrets, 16, macSecret, 0, macSecret.length);
    return macSecret;
  }

  private static byte[] generateEncryptionSecret() {
    try {
      KeyGenerator generator = KeyGenerator.getInstance("AES");
      generator.init(128);

      SecretKey key = generator.generateKey();
      return key.getEncoded();
    } catch (NoSuchAlgorithmException ex) {
      Log.w("keyutil", ex);
      return null;
    }
  }

  private static byte[] generateMacSecret() {
    try {
      KeyGenerator generator = KeyGenerator.getInstance("HmacSHA1");
      return generator.generateKey().getEncoded();
    } catch (NoSuchAlgorithmException e) {
      Log.w("keyutil", e);
      return null;
    }
  }

  private static byte[] generateSalt() throws NoSuchAlgorithmException {
    SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
    byte[] salt         = new byte[8];
    random.nextBytes(salt);

    return salt;
  }

}
